﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace BitcoinLibrary
{
    /**
    * A transfer of coins from one address to another creates a transaction in which the outputs
    * can be claimed by the recipient in the input of another transaction. You can imagine a
    * transaction as being a module which is wired up to others, the inputs of one have to be wired
    * to the outputs of another. The exceptions are coinbase transactions, which create new coins.
    */
    public class TransactionInput : Message /*implements Serializable*/
    {
        private const long serialVersionUID = 2;
        //public const byte[] EMPTY_ARRAY = new byte[] {};

        // Allows for altering transactions after they were broadcast. Tx replacement is currently disabled in the C++
        // client so this is always the UINT_MAX.
        // TODO: Document this in more detail and build features that use it.
        long sequence;
        // Data needed to connect to the output of the transaction we're gathering coins from.
        internal TransactionOutPoint outpoint;
        // The "script bytes" might not actually be a script. In coinbase transactions where new coins are minted there
        // is no input transaction, so instead the scriptBytes contains some extra stuff (like a rollover nonce) that we
        // don't care about much. The bytes are turned into a Script object (cached below) on demand via a getter.
        internal byte[] scriptBytes;
        // The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not
        // coinbase.
        private Script scriptSig;
        // A pointer to the transaction that owns this input.
        public Transaction parentTransaction;

        /** Used only in creation of the genesis block. */
        public TransactionInput(NetworkParameters param, Transaction parentTransaction, byte[] scriptBytes)
            : base(param)
        {
            this.scriptBytes = scriptBytes;
            this.outpoint = new TransactionOutPoint(param, -1, null);
            this.sequence = 0xFFFFFFFFL;
            this.parentTransaction = parentTransaction;
        }

        /** Creates an UNSIGNED input that links to the given output */
        public TransactionInput(NetworkParameters param, Transaction parentTransaction, TransactionOutput output)
            : base(param)
        {
            long outputIndex = output.getIndex();
            outpoint = new TransactionOutPoint(param, outputIndex, output.parentTransaction);
            scriptBytes = new byte[] { };// EMPTY_ARRAY;
            sequence = 0xFFFFFFFFL;
            this.parentTransaction = parentTransaction;
        }

        /** Deserializes an input message. This is usually part of a transaction message. */
        public TransactionInput(NetworkParameters param, Transaction parentTransaction,
        byte[] payload, int offset)
            : base(param, payload, offset)
        {
            this.parentTransaction = parentTransaction;
        }

        public override void parse()
        {
            outpoint = new TransactionOutPoint(param, bytes, cursor);
            cursor += outpoint.getMessageSize();
            int scriptLen = (int)readVarInt();
            scriptBytes = readBytes(scriptLen);
            sequence = readUint32();
        }

        public override void bitcoinSerializeToStream(Stream stream)
        {
            outpoint.bitcoinSerializeToStream(stream);
            var data = new VarInt(scriptBytes.Length).encode();
            stream.Write(data, 0, data.Length);
            stream.Write(scriptBytes, 0, scriptBytes.Length);
            Utils.uint32ToByteStreamLE(sequence, stream);
        }

        /**
        * Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true.
        */
        public bool isCoinBase()
        {
            for (int i = 0; i < outpoint.hash.Length; i++)
                if (outpoint.hash[i] != 0) return false;
            return true;
        }

        /**
        * Returns the input script.
        */
        public Script getScriptSig()
        {
            // Transactions that generate new coins don't actually have a script. Instead this
            // parameter is overloaded to be something totally different.
            if (scriptSig == null)
            {
                Debug.Assert(scriptBytes != null);
                scriptSig = new Script(param, scriptBytes, 0, scriptBytes.Length);
            }
            return scriptSig;
        }

        /**
        * Convenience method that returns the from address of this input by parsing the scriptSig.
        * @throws ScriptException if the scriptSig could not be understood (eg, if this is a coinbase transaction).
        */
        public Address getFromAddress()
        {
            Debug.Assert(!isCoinBase());
            return getScriptSig().getFromAddress();
        }


        /** Returns a human readable debug string. */
        public override string ToString()
        {
            if (isCoinBase())
                return "TxIn: COINBASE";
            try
            {
                return "TxIn from " + Utils.bytesToHexString(getScriptSig().getPubKey()) + " script:" +
                getScriptSig().ToString();
            }
            catch (ScriptException e)
            {
                throw e;
            }
        }

        internal enum ConnectionResult
        {
            NO_SUCH_TX,
            ALREADY_SPENT,
            SUCCESS
        }

        // TODO: Clean all this up once TransactionOutPoint disappears.

        /**
        * Locates the referenced output from the given pool of transactions.
        * @return The TransactionOutput or null if the transactions map doesn't contain the referenced tx.
        */
        internal TransactionOutput getConnectedOutput(Dictionary<Sha256Hash, Transaction> transactions)
        {
            Sha256Hash h = new Sha256Hash(outpoint.hash);
            Transaction tx = transactions[h];
            if (tx == null)
                return null;
            TransactionOutput to = tx.outputs[(int)outpoint.index];
            return to;
        }

        /**
        * Connects this input to the relevant output of the referenced transaction if it's in the given map.
        * Connecting means updating the internal pointers and spent flags.
        *
        * @param transactions Map of txhash->transaction.
        * @param disconnect Whether to abort if there's a pre-existing connection or not.
        * @return true if connection took place, false if the referenced transaction was not in the list.
        */
        internal ConnectionResult connect(Dictionary<Sha256Hash, Transaction> transactions, bool disconnect)
        {
            Sha256Hash h = new Sha256Hash(outpoint.hash);
            Transaction tx = transactions[h];
            if (tx == null)
                return TransactionInput.ConnectionResult.NO_SUCH_TX;
            TransactionOutput to = tx.outputs[(int)outpoint.index];
            if (!to.isAvailableForSpending())
            {
                if (disconnect)
                    to.markAsUnspent();
                else
                    return TransactionInput.ConnectionResult.ALREADY_SPENT;
            }
            outpoint.fromTx = tx;
            to.markAsSpent(this);
            return TransactionInput.ConnectionResult.SUCCESS;
        }

        /**
        * Release the connected output, making it spendable once again.
        *
        * @return true if the disconnection took place, false if it was not connected.
        */
        internal bool disconnect()
        {
            if (outpoint.fromTx == null) return false;
            outpoint.fromTx.outputs[(int)outpoint.index].markAsUnspent();
            outpoint.fromTx = null;
            return true;
        }
    }
}
